﻿using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace System.Drawing
{
    public class SpeedBitmap : IDisposable
    {
        private readonly Bitmap f_Bitmap;
        private readonly int f_Height;
        private readonly bool f_IsAlpha;
        private readonly int f_Width;
        private int f_A;
        private int f_B1;
        private BitmapData f_BmpData;
        private IntPtr f_BmpPtr;
        private int f_G;
        private int f_G1;
        private int f_Index;
        private int f_R1;
        private byte[] f_RGBValues;
        private int f_b;
        private int f_r;

        public SpeedBitmap(Bitmap bitmap)
        {
            if(bitmap == null)
                throw new ArgumentNullException();
            f_Bitmap = bitmap;
            if(f_Bitmap.PixelFormat == (PixelFormat.Indexed | f_Bitmap.PixelFormat))
                throw new ArgumentException("Can't work with indexed pixel format");
            f_IsAlpha = bitmap.PixelFormat == (bitmap.PixelFormat | PixelFormat.Alpha);
            f_Width = bitmap.Width;
            f_Height = bitmap.Height;
            Lock();
        }

        public Color this[Point p] { get { return this[p.X, p.Y]; } set { this[p.X, p.Y] = value; } }

        public Color this[int x, int y] { get { return GetPixel(x, y); } set { SetPixel(x, y, value); } }

        public int ColorDiff(Point point1, Point point2) { return ColorDiff(point1.X, point1.Y, point2.X, point2.Y); }

        public int ColorDiff(int i1, int i2)
        {
            if(f_IsAlpha)
            {
                f_Index = i1 * 4;
                f_b = f_RGBValues[f_Index];
                f_G = f_RGBValues[f_Index + 1];
                f_r = f_RGBValues[f_Index + 2];
                f_A = f_RGBValues[f_Index + 3];
            }
            else
            {
                f_Index = i1 * 3;
                f_b = f_RGBValues[f_Index];
                f_G = f_RGBValues[f_Index + 1];
                f_r = f_RGBValues[f_Index + 2];
            }
            if(f_IsAlpha)
            {
                f_Index = i2 * 4;
                f_B1 = f_RGBValues[f_Index];
                f_G1 = f_RGBValues[f_Index + 1];
                f_R1 = f_RGBValues[f_Index + 2];
            }
            else
            {
                f_Index = i2 * 3;
                f_B1 = f_RGBValues[f_Index];
                f_G1 = f_RGBValues[f_Index + 1];
                f_R1 = f_RGBValues[f_Index + 2];
            }
            f_r = f_R1 - f_r;
            f_G = f_G1 - f_G;
            f_b = f_B1 - f_b;
            if(f_r < 0) f_r = -f_r;
            if(f_G < 0) f_G = -f_G;
            if(f_b < 0) f_b = -f_b;
            return f_r + f_G + f_b;
        }

        public int ColorDiff(int x1, int y1, int x2, int y2)
        {
            if(f_IsAlpha)
            {
                f_Index = (y1 * f_Width + x1) * 4;
                f_b = f_RGBValues[f_Index];
                f_G = f_RGBValues[f_Index + 1];
                f_r = f_RGBValues[f_Index + 2];
                f_A = f_RGBValues[f_Index + 3];
            }
            else
            {
                f_Index = ((y1 * f_Width) + x1) * 3;
                f_b = f_RGBValues[f_Index];
                f_G = f_RGBValues[f_Index + 1];
                f_r = f_RGBValues[f_Index + 2];
            }
            if(f_IsAlpha)
            {
                f_Index = ((y2 * f_Width) + x2) * 4;
                f_B1 = f_RGBValues[f_Index];
                f_G1 = f_RGBValues[f_Index + 1];
                f_R1 = f_RGBValues[f_Index + 2];
            }
            else
            {
                f_Index = ((y2 * f_Width) + x2) * 3;
                f_B1 = f_RGBValues[f_Index];
                f_G1 = f_RGBValues[f_Index + 1];
                f_R1 = f_RGBValues[f_Index + 2];
            }
            f_r = f_R1 - f_r;
            f_G = f_G1 - f_G;
            f_b = f_B1 - f_b;
            if(f_r < 0) f_r = -f_r;
            if(f_G < 0) f_G = -f_G;
            if(f_b < 0) f_b = -f_b;
            return f_r + f_G + f_b;
        }

        public Color GetPixel(Point point) { return GetPixel(point.X, point.Y); }

        public Color GetPixel(int x, int y)
        {
            if((x > (f_Width - 1)) || (y > (f_Height - 1)))
                throw new ArgumentException();

            if(f_IsAlpha)
            {
                f_Index = ((y * f_Width) + x) * 4;
                f_b = f_RGBValues[f_Index];
                f_G = f_RGBValues[f_Index + 1];
                f_r = f_RGBValues[f_Index + 2];
                f_A = f_RGBValues[f_Index + 3];
                return Color.FromArgb(f_A, f_r, f_G, f_b);
            }
            f_Index = ((y * f_Width) + x) * 3;
            f_b = f_RGBValues[f_Index];
            f_G = f_RGBValues[f_Index + 1];
            f_r = f_RGBValues[f_Index + 2];
            return Color.FromArgb(f_r, f_G, f_b);
        }

        private void Lock()
        {
            var rect = new Rectangle(0, 0, f_Width, f_Height);
            f_BmpData = f_Bitmap.LockBits(rect, ImageLockMode.ReadWrite, f_Bitmap.PixelFormat);
            f_BmpPtr = f_BmpData.Scan0;
            var num = (f_Width * f_Height) * (f_IsAlpha ? 4 : 3);
            f_RGBValues = new byte[num];
            Marshal.Copy(f_BmpPtr, f_RGBValues, 0, f_RGBValues.Length);
        }

        public static implicit operator SpeedBitmap(Bitmap bmp)
        {
            return new SpeedBitmap(bmp);
        }

        public static implicit operator Bitmap(SpeedBitmap sb)
        {
            return sb.f_Bitmap;
        }

        public void SetPixel(Point point, Color color)
        {
            if(f_IsAlpha)
            {
                f_Index = (point.Y * f_Width + point.X) * 4;
                f_RGBValues[f_Index] = color.B;
                f_RGBValues[f_Index + 1] = color.G;
                f_RGBValues[f_Index + 2] = color.R;
                f_RGBValues[f_Index + 3] = color.A;
            }
            else
            {
                f_Index = ((point.Y * f_Width) + point.X) * 3;
                f_RGBValues[f_Index] = color.B;
                f_RGBValues[f_Index + 1] = color.G;
            }
        }

        public void SetPixel(int x, int y, Color color)
        {
            if(f_IsAlpha)
            {
                f_Index = (y * f_Width + x) * 4;
                f_RGBValues[f_Index] = color.B;
                f_RGBValues[f_Index + 1] = color.G;
                f_RGBValues[f_Index + 2] = color.R;
                f_RGBValues[f_Index + 3] = color.A;
            }
            else
            {
                f_Index = (y * f_Width + x) * 3;
                f_RGBValues[f_Index] = color.B;
                f_RGBValues[f_Index + 1] = color.G;
                f_RGBValues[f_Index + 2] = color.R;
            }
        }

        public void SetPixel(int x, int y, int r, int g, int b)
        {
            if(f_IsAlpha)
            {
                f_Index = ((y * f_Width) + x) * 4;
                f_RGBValues[f_Index] = (byte)b;
                f_RGBValues[f_Index + 1] = (byte)g;
                f_RGBValues[f_Index + 2] = (byte)g;
                f_RGBValues[f_Index + 3] = 0xff;
            }
            else
            {
                f_Index = ((y * f_Width) + x) * 3;
                f_RGBValues[f_Index] = (byte)b;
                f_RGBValues[f_Index + 1] = (byte)g;
                f_RGBValues[f_Index + 2] = (byte)r;
            }
        }

        private void UnLock()
        {
            Marshal.Copy(f_RGBValues, 0, f_BmpPtr, f_RGBValues.Length);
            f_Bitmap.UnlockBits(f_BmpData);
        }

        public void Dispose() { UnLock(); }
    }
}